package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.repository.AlbumIndexRepository;
import com.atguigu.tingshu.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumListVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;


import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Autowired
    private CategoryFeignClient categoryFeignClient;
    @Autowired
    private UserInfoFeignClient userInfoFeignClient;
    @Autowired
    private AlbumIndexRepository albumIndexRepository;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private SuggestIndexRepository suggestIndexRepository;
    @Value("${ranking.dimension.array}")
    private String rankingDimensionArray;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public void upperAlbum(Long albumId) {
        AlbumInfoIndex infoIndex = new AlbumInfoIndex();
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //根据id查询专辑信息并赋值
            Result<AlbumInfo> result = albumInfoFeignClient.getAlbumInfo(albumId);
            Assert.notNull(result, "查询信息失败");
            AlbumInfo albuminfo = result.getData();
            Assert.notNull(albuminfo, "查询信息失败");
            BeanUtils.copyProperties(albuminfo, infoIndex);
            //赋值属性信息
            List<AlbumAttributeValue> attributeValueVoList = albuminfo.getAlbumAttributeValueVoList();
            if (!CollectionUtils.isEmpty(attributeValueVoList)) {
                List<AttributeValueIndex> valueIndexList = attributeValueVoList.stream().map(attributeValue -> {
                    AttributeValueIndex valueIndex = new AttributeValueIndex();
                    valueIndex.setAttributeId(attributeValue.getAttributeId());
                    valueIndex.setValueId(attributeValue.getValueId());
                    return valueIndex;
                }).collect(Collectors.toList());
                //赋值属性列表
                infoIndex.setAttributeValueIndexList(valueIndexList);
            }
            return albuminfo;
        }, threadPoolExecutor).exceptionally(throwable -> {
            log.error("查询专辑信息失败: {}", throwable.getMessage());
            return null;
        });
        CompletableFuture<Integer> categoryCompletableFuture = albumInfoCompletableFuture.thenApplyAsync(albumInfo -> {
            //远程调用获取分类对象
            Result<BaseCategoryView> result = categoryFeignClient.getCategoryView(albumInfo.getCategory3Id());
            Assert.notNull(result, "查询分类对象失败");
            BaseCategoryView baseCategoryView = result.getData();
            Assert.notNull(baseCategoryView, "查询分类对象失败");
            infoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            infoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
            return 1;
        }, threadPoolExecutor).exceptionally(throwable -> {
            log.error("查询分类对象失败: {}" + throwable.getMessage());
            return 0;
        });
        CompletableFuture<Integer> userInfoVoCompletableFuture = albumInfoCompletableFuture.thenApplyAsync(albumInfo -> {
            //查询作者信息
            Result<UserInfoVo> result = userInfoFeignClient.getUserInfoVo(albumInfo.getUserId());
            Assert.notNull(result, "查询作者信息失败");
            UserInfoVo userInfoVo = result.getData();
            Assert.notNull(userInfoVo, "查询作者信息失败");
            return 1;
        }, threadPoolExecutor).exceptionally(throwable -> {
            log.error("查询作者信息失败");
            return 0;
        });
//        CompletableFuture<Integer> statusCompletableFuture = albumInfoCompletableFuture.thenApplyAsync(albumInfo -> {
//            Result<AlbumListVo> result = albumInfoFeignClient.getStatusByAlbumId(albumInfo.getId());
//            Assert.notNull(result, "调用专辑统计信息失败");
//            AlbumListVo albumListVo = result.getData();
//            Assert.notNull(albumListVo, "调用专辑统计失败");
//            infoIndex.setPlayStatNum(albumListVo.getPlayStatNum());
//            infoIndex.setSubscribeStatNum(albumListVo.getSubscribeStatNum());
//            infoIndex.setBuyStatNum(albumListVo.getBuyStatNum());
//            infoIndex.setCommentStatNum(albumListVo.getCommentStatNum());
//            double hotScore =
//                    albumListVo.getPlayStatNum() * 0.4 +
//                    albumListVo.getSubscribeStatNum() * 0.3 +
//                    albumListVo.getCommentStatNum() * 0.2 +
//                    albumListVo.getBuyStatNum() * 0.1;
//            infoIndex.setHotScore(hotScore);
//            return 1;
//        }, threadPoolExecutor).exceptionally(throwable -> {
//            log.error("调用专辑统计信息失败");
//            return 0;
//        });
        //  统计信息: 根据专辑Id 查询专辑的统计数据;
        infoIndex.setPlayStatNum(new Random().nextInt(10000));
        infoIndex.setSubscribeStatNum(new Random().nextInt(10000));
        infoIndex.setBuyStatNum(new Random().nextInt(10000));
        infoIndex.setCommentStatNum(new Random().nextInt(10000));
        //  设置热度排名;
        infoIndex.setHotScore(Double.valueOf(new Random().nextInt(10000)));
        try {
            if (null == albumInfoCompletableFuture.get() || 0 == categoryCompletableFuture.get() ||
                    0 == userInfoVoCompletableFuture.get()) {
                //  获取专辑失败;
                throw new GuiguException(20001, "远程调用获取数据失败");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        albumIndexRepository.save(infoIndex);
        //上架添加题词数据
        //  上架提出库;| keyword |
        SuggestIndex suggestIndex = new SuggestIndex();
        //  title;
        suggestIndex.setId(UUID.randomUUID().toString().replace("-", ""));
        suggestIndex.setTitle(infoIndex.getAlbumTitle());
        suggestIndex.setKeyword(new Completion(new String[]{infoIndex.getAlbumTitle()}));
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(infoIndex.getAlbumTitle())}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(infoIndex.getAlbumTitle())}));
        suggestIndexRepository.save(suggestIndex);
        //  介绍;
        SuggestIndex suggestIndexInfo = new SuggestIndex();
        //  介绍
        suggestIndexInfo.setId(UUID.randomUUID().toString().replace("-", ""));
        suggestIndexInfo.setTitle(infoIndex.getAlbumIntro());
        suggestIndexInfo.setKeyword(new Completion(new String[]{infoIndex.getAlbumIntro()}));
        suggestIndexInfo.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(infoIndex.getAlbumIntro())}));
        suggestIndexInfo.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(infoIndex.getAlbumIntro())}));
        suggestIndexRepository.save(suggestIndexInfo);
//        // 作者
//        SuggestIndex announcerSuggestIndex = new SuggestIndex();
//        announcerSuggestIndex.setId(UUID.randomUUID().toString().replaceAll("-", ""));
//        announcerSuggestIndex.setTitle(infoIndex.getAnnouncerName());
//        announcerSuggestIndex.setKeyword(new Completion(new String[]{infoIndex.getAnnouncerName()}));
//        announcerSuggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(infoIndex.getAnnouncerName())}));
//        announcerSuggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(infoIndex.getAnnouncerName())}));
//        suggestIndexRepository.save(announcerSuggestIndex);
    }

    @Override
    public void lowerAlbum(Long albumId) {
        albumIndexRepository.deleteById(albumId);
    }

    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        //根据一级分类id获取三级分类列表
        Result<List<BaseCategory3>> listResult = categoryFeignClient.findTopBaseCategory3(category1Id);
        Assert.notNull(listResult, "查询三级分类列表失败");
        List<BaseCategory3> baseCategory3List = listResult.getData();
        Assert.notNull(baseCategory3List, "查询三级分类列表失败");
        Map<Long, BaseCategory3> category3Map = baseCategory3List.stream().collect(Collectors.toMap(BaseCategory3::getId, Function.identity()));
        // 根据三级分类id获取专辑列表
        List<Long> category3IdList = baseCategory3List.stream().map(BaseCategory3::getId).collect(Collectors.toList());
        List<FieldValue> fieldValueList = baseCategory3List.stream().map(baseCategory3 -> FieldValue.of(baseCategory3.getId())).collect(Collectors.toList());
        SearchResponse<AlbumInfoIndex> response = null;
        try {
            response = elasticsearchClient.search(s -> s.index("albuminfo")
                            .query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                            .aggregations("agg_category3Id", a -> a.terms(t -> t.field("category3Id"))
                                    .aggregations("topSixHotScoreAgg", a1 -> a1.topHits(t1 -> t1.size(6)
                                            .sort(s1 -> s1.field(f1 -> f1.order(SortOrder.Desc).field("hotScore"))))))
                    , AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  获取结果组装数据;
        Aggregate aggregate = response.aggregations().get("agg_category3Id");
        List<Map<String, Object>> list = aggregate.lterms().buckets().array().stream().map(bucket -> {
            //  存储数据；需要创建一个map集合：
            HashMap<String, Object> map = new HashMap<>();
            //  先获取key;
            long category3Id = bucket.key();
            //  存储第一个数据三级分类对象： baseCategory3.name
            map.put("baseCategory3", category3Map.get(category3Id));
            //  获取专辑集合列表;
            Aggregate topSixHotScoreAgg = bucket.aggregations().get("topSixHotScoreAgg");
            List<AlbumInfoIndex> albumInfoIndexList = topSixHotScoreAgg.topHits().hits().hits().stream()
                    .map(hit -> JSON.parseObject(hit.source().toString(), AlbumInfoIndex.class)).collect(Collectors.toList());
            map.put("list", albumInfoIndexList);
            //  返回map;
            return map;
        }).collect(Collectors.toList());
        return list;
    }

    @Override
    public List<String> completeSuggest(String keyword) {
        SearchResponse<SuggestIndex> response = null;
        try {
            response = elasticsearchClient.search(
                    searchRequest -> searchRequest
                            .index("suggestinfo")
                            .suggest(s -> s.suggesters("suggestionKeyword",
                                            f -> f.prefix(keyword).completion(c -> c
                                                    .field("keyword")
                                                    .skipDuplicates(true)
                                                    .size(10)
                                                    .fuzzy(fu -> fu.fuzziness("AUTO"))))
                                    .suggesters("suggestionKeywordPinyin",
                                            f -> f.prefix(keyword).completion(c -> c
                                                    .field("keywordPinyin")
                                                    .skipDuplicates(true)
                                                    .size(10)
                                                    .fuzzy(fu -> fu.fuzziness("AUTO"))))
                                    .suggesters("suggestionkeywordSequence",
                                            f -> f.prefix(keyword).completion(c -> c
                                                    .field("keywordSequence")
                                                    .skipDuplicates(true)
                                                    .size(10)
                                                    .fuzzy(fu -> fu.fuzziness("AUTO"))))
                            )
                    , SuggestIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        List<String> list = new ArrayList<>();
        list.addAll(this.getSuggestList(response, "suggestionKeyword"));
        list.addAll(this.getSuggestList(response, "suggestionkeywordPinyin"));
        list.addAll(this.getSuggestList(response, "suggestionkeywordSequence"));
        return list;
    }

    private Collection<String> getSuggestList(SearchResponse<SuggestIndex> response, String suggestKeyword) {
        List<Suggestion<SuggestIndex>> suggestionList = response.suggest().get(suggestKeyword);
        if (!CollectionUtils.isEmpty(suggestionList)) {
            for (Suggestion<SuggestIndex> indexSuggestion : suggestionList) {
                return indexSuggestion.completion().options().stream().map(option -> option.source().getTitle()).collect(
                        Collectors.toList()
                );
            }
        }
        return new ArrayList<>();
    }

    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //先获取用户检索的DSL语句
        SearchRequest request = this.buildQueryDsl(albumIndexQuery);
        //检索并赋值
        SearchResponse<AlbumInfoIndex> response = null;
        try {
            response = elasticsearchClient.search(request, AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        AlbumSearchResponseVo albumSearchResponseVo = this.parseResultData(response);
        albumSearchResponseVo.setTotal(response.hits().total().value());
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        albumSearchResponseVo.setTotalPages((albumSearchResponseVo.getTotal() + albumIndexQuery.getPageSize() - 1) / albumIndexQuery.getPageSize());
        return albumSearchResponseVo;
    }

    private SearchRequest buildQueryDsl(AlbumIndexQuery albumIndexQuery) {
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        searchRequestBuilder.index("albuminfo");
        if (!ObjectUtils.isEmpty(albumIndexQuery.getKeyword())) {
            //根据关键词查询
            boolQueryBuilder.should(m -> m.match(f -> f.field("albumTitle").query(albumIndexQuery.getKeyword())))
                    .should(m -> m.match(f -> f.field("albumIntro").query(albumIndexQuery.getKeyword())))
                    .should(m -> m.match(f -> f.field("announcerName").query(albumIndexQuery.getKeyword())));
        }
        //分类id
        if (!ObjectUtils.isEmpty(albumIndexQuery.getCategory1Id())) {
            boolQueryBuilder.filter(t -> t.term(f -> f.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        if (!ObjectUtils.isEmpty(albumIndexQuery.getCategory2Id())) {
            boolQueryBuilder.filter(t -> t.term(f -> f.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        if (!ObjectUtils.isEmpty(albumIndexQuery.getCategory3Id())) {
            boolQueryBuilder.filter(t -> t.term(f -> f.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }
        //通过属性值过滤
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            for (String attribute : attributeList) {
                String split[] = attribute.split(":");
                if (null != split && 2 == split.length) {
                    boolQueryBuilder.filter(f -> f.nested(p -> p.path("attributeValueIndexList")
                            .query(q -> q.bool(b -> b.must(m -> m.term(t -> t.field("attributeValueIndexlist.attribute").value(split[0])))
                                    .must(m -> m.term(t -> t.field("attributeValueIndexList.value").value(split[1]))))
                            ))
                    );
                }
            }
        }
        //sort分页设置
        Integer pageSize = albumIndexQuery.getPageSize();
        Integer from = (albumIndexQuery.getPageNo() - 1) * pageSize;
        searchRequestBuilder.from(from);
        searchRequestBuilder.size(pageSize);
        //排序
        String order = albumIndexQuery.getOrder();
        if (!ObjectUtils.isEmpty(order)) {
            String split[] = order.split(":");
            if (null != split && 2 == split.length) {
                String field = "";
                switch (split[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "playStatNum";
                        break;
                    case "3":
                        field = "createTime";
                        break;
                }
                String finalField = field;
                searchRequestBuilder.sort(s -> s.field(f -> f.field(finalField).order("asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc)));
            }
        }
        //设置高亮
        searchRequestBuilder.highlight(h -> h.fields("albumTitle",
                f -> f.preTags("<span style=color:red>").postTags("</span>")));
        searchRequestBuilder.query(boolQueryBuilder.build()._toQuery());
        SearchRequest searchRequest = searchRequestBuilder.build();
        //  打印DSL语句：
        System.out.println("DSL:\t" + searchRequest.toString());
        return searchRequest;
    }

    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long category1Id, String dimension) {
//        return (List<AlbumInfoIndexVo>)redisTemplate.opsForHash().get(RedisConstant.RANKING_KEY_PREFIX + category1Id, dimension);
        return (List<AlbumInfoIndexVo>)redisTemplate.boundHashOps(RedisConstant.RANKING_KEY_PREFIX + category1Id).get( dimension);
    }

    @Override
    public void  updateLatelyAlbumRanking() {
        Result<List<BaseCategory1>> result = categoryFeignClient.findCategory1All();
        Assert.notNull(result, "获取一级分类列表失败！");
        List<BaseCategory1> baseCategory1List = result.getData();
        Assert.notNull(baseCategory1List, "获取一级分类列表失败！");
        String[] split = rankingDimensionArray.split(",");
        for (BaseCategory1 category1 : baseCategory1List) {
            Long category1Id = category1.getId();
            for (String dimension : split) {
                SearchResponse<AlbumInfoIndex> response = null;
                try {
                    SearchRequest.Builder s = new SearchRequest.Builder();
                    SearchRequest searchRequest = s.index("albuminfo")
                            .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                            .sort(s1 -> s1.field(f -> f.field(dimension).order(SortOrder.Desc)))
                            .size(10).build();
                    response = elasticsearchClient.search(
                            searchRequest
//                            s -> s.index("albuminfo")
//                                    .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
//                                    .sort(s1 -> s1.field(f -> f.field(dimension).order(SortOrder.Desc)))
//                                    .size(10)
                            , AlbumInfoIndex.class);
                    log.info("更新排行榜：{}", searchRequest.toString());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                List<AlbumInfoIndex> albumInfoIndexList = response.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
//                redisTemplate.opsForHash().put(RedisConstant.RANKING_KEY_PREFIX + category1Id, dimension, albumInfoIndexList);
                redisTemplate.boundHashOps(RedisConstant.RANKING_KEY_PREFIX + category1Id).put(dimension, albumInfoIndexList);

            }
        }

    }

    private AlbumSearchResponseVo parseResultData(SearchResponse<AlbumInfoIndex> response) {
        //创建一个对象
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        List<AlbumInfoIndexVo> list = new ArrayList<>();
        List<Hit<AlbumInfoIndex>> hits = response.hits().hits();
        //获取数据循环遍历
        hits.stream().map(albumInfoIndexHit -> {
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            AlbumInfoIndex albumInfoIndex = albumInfoIndexHit.source();
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            //判断是否有高亮，有的话复制给albumInfoIndexVo,返回给前端
            if (null != albumInfoIndexHit.highlight().get("albumTitle")) {
                String albumTitle = albumInfoIndexHit.highlight().get("albumTitle").get(0);
                albumInfoIndexVo.setAlbumTitle(albumTitle);
            }
            list.add(albumInfoIndexVo);
            return albumInfoIndexVo;
        }).collect(Collectors.toList());
        albumSearchResponseVo.setList(list);
        return albumSearchResponseVo;
    }
}
